/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Shader built-in constant tests.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderBuiltinConstantTests.hpp"
#include "glsShaderExecUtil.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "tcuTestLog.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

using std::string;
using std::vector;
using tcu::TestLog;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static int getInteger (const glw::Functions& gl, deUint32 pname)
{
	int value = -1;
	gl.getIntegerv(pname, &value);
	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
	return value;
}

template<deUint32 Pname>
static int getInteger (const glw::Functions& gl)
{
	return getInteger(gl, Pname);
}

static int getVectorsFromComps (const glw::Functions& gl, deUint32 pname)
{
	int value = -1;
	gl.getIntegerv(pname, &value);
	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
	TCU_CHECK_MSG(value%4 == 0, ("Expected " + glu::getGettableStateStr((int)pname).toString() + " to be divisible by 4").c_str());
	return value/4;
}

template<deUint32 Pname>
static int getVectorsFromComps (const glw::Functions& gl)
{
	return getVectorsFromComps(gl, Pname);
}

static tcu::IVec3 getIVec3 (const glw::Functions& gl, deUint32 pname)
{
	tcu::IVec3 value(-1);
	for (int ndx = 0; ndx < 3; ndx++)
		gl.getIntegeri_v(pname, (glw::GLuint)ndx, &value[ndx]);
	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegeri_v(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
	return value;
}

template<deUint32 Pname>
static tcu::IVec3 getIVec3 (const glw::Functions& gl)
{
	return getIVec3(gl, Pname);
}

static std::string makeCaseName (const std::string& varName)
{
	DE_ASSERT(varName.length() > 3);
	DE_ASSERT(varName.substr(0,3) == "gl_");

	std::ostringstream name;
	name << de::toLower(char(varName[3]));

	for (size_t ndx = 4; ndx < varName.length(); ndx++)
	{
		const char c = char(varName[ndx]);
		if (de::isUpper(c))
			name << '_' << de::toLower(c);
		else
			name << c;
	}

	return name.str();
}

enum
{
	VS = (1<<glu::SHADERTYPE_VERTEX),
	TC = (1<<glu::SHADERTYPE_TESSELLATION_CONTROL),
	TE = (1<<glu::SHADERTYPE_TESSELLATION_EVALUATION),
	GS = (1<<glu::SHADERTYPE_GEOMETRY),
	FS = (1<<glu::SHADERTYPE_FRAGMENT),
	CS = (1<<glu::SHADERTYPE_COMPUTE),

	SHADER_TYPES = VS|TC|TE|GS|FS|CS
};

template<typename DataType>
class ShaderBuiltinConstantCase : public TestCase
{
public:
	typedef DataType (*GetConstantValueFunc) (const glw::Functions& gl);

								ShaderBuiltinConstantCase	(Context& context, const char* varName, GetConstantValueFunc getValue, const char* requiredExt);
								~ShaderBuiltinConstantCase	(void);

	void						init						(void);
	IterateResult				iterate						(void);

private:
	bool						verifyInShaderType			(glu::ShaderType shaderType, DataType reference);

	const std::string			m_varName;
	const GetConstantValueFunc	m_getValue;
	const std::string			m_requiredExt;
};

template<typename DataType>
ShaderBuiltinConstantCase<DataType>::ShaderBuiltinConstantCase (Context& context, const char* varName, GetConstantValueFunc getValue, const char* requiredExt)
	: TestCase		(context, makeCaseName(varName).c_str(), varName)
	, m_varName		(varName)
	, m_getValue	(getValue)
	, m_requiredExt	(requiredExt ? requiredExt : "")
{
	DE_ASSERT(!requiredExt == m_requiredExt.empty());
}

template<typename DataType>
ShaderBuiltinConstantCase<DataType>::~ShaderBuiltinConstantCase (void)
{
}

template<typename DataType>
void ShaderBuiltinConstantCase<DataType>::init (void)
{
	if (!m_requiredExt.empty() && !m_context.getContextInfo().isExtensionSupported(m_requiredExt.c_str()))
		throw tcu::NotSupportedError(m_requiredExt + " not supported");
}

static gls::ShaderExecUtil::ShaderExecutor* createGetConstantExecutor (const glu::RenderContext&	renderCtx,
																	   glu::ShaderType				shaderType,
																	   glu::DataType				dataType,
																	   const std::string&			varName,
																	   const std::string&			extName)
{
	using namespace gls::ShaderExecUtil;

	ShaderSpec	shaderSpec;

	shaderSpec.version	= glu::GLSL_VERSION_310_ES;
	shaderSpec.source	= string("result = ") + varName + ";\n";

	shaderSpec.outputs.push_back(Symbol("result", glu::VarType(dataType, glu::PRECISION_HIGHP)));

	if (!extName.empty())
		shaderSpec.globalDeclarations = "#extension " + extName + " : require\n";

	return createExecutor(renderCtx, shaderType, shaderSpec);
}

template<typename DataType>
static void logVarValue (tcu::TestLog& log, const std::string& varName, DataType value)
{
	log << TestLog::Message << varName << " = " << value << TestLog::EndMessage;
}

template<>
void logVarValue<int> (tcu::TestLog& log, const std::string& varName, int value)
{
	log << TestLog::Integer(varName, varName, "", QP_KEY_TAG_NONE, value);
}

template<typename DataType>
bool ShaderBuiltinConstantCase<DataType>::verifyInShaderType (glu::ShaderType shaderType, DataType reference)
{
	using namespace gls::ShaderExecUtil;

	const de::UniquePtr<ShaderExecutor>	shaderExecutor	(createGetConstantExecutor(m_context.getRenderContext(), shaderType, glu::dataTypeOf<DataType>(), m_varName, m_requiredExt));
	DataType							result			= DataType(-1);
	void* const							outputs			= &result;

	if (!shaderExecutor->isOk())
	{
		shaderExecutor->log(m_testCtx.getLog());
		TCU_FAIL("Compile failed");
	}

	shaderExecutor->useProgram();
	shaderExecutor->execute(1, DE_NULL, &outputs);

	logVarValue(m_testCtx.getLog(), m_varName, result);

	if (result != reference)
	{
		m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << m_varName << " = " << reference << TestLog::EndMessage
						   << TestLog::Message << "Test shader:" << TestLog::EndMessage;
		shaderExecutor->log(m_testCtx.getLog());
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid builtin constant value");
		return false;
	}
	else
		return true;
}

template<typename DataType>
TestCase::IterateResult ShaderBuiltinConstantCase<DataType>::iterate (void)
{
	const DataType	reference	= m_getValue(m_context.getRenderContext().getFunctions());

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
	{
		if ((SHADER_TYPES & (1<<shaderType)) != 0)
		{
			const char* const			shaderTypeName	= glu::getShaderTypeName(glu::ShaderType(shaderType));
			const tcu::ScopedLogSection	section			(m_testCtx.getLog(), shaderTypeName, shaderTypeName);

			try
			{
				const bool isOk = verifyInShaderType(glu::ShaderType(shaderType), reference);
				DE_ASSERT(isOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
				DE_UNREF(isOk);
			}
			catch (const tcu::NotSupportedError& e)
			{
				m_testCtx.getLog() << TestLog::Message << "Not checking " << shaderTypeName << ": " << e.what() << TestLog::EndMessage;
			}
			catch (const tcu::TestError& e)
			{
				m_testCtx.getLog() << TestLog::Message << e.what() << TestLog::EndMessage;
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, e.getMessage());
			}
		}
	}

	return STOP;
}

} // anonymous

ShaderBuiltinConstantTests::ShaderBuiltinConstantTests (Context& context)
	: TestCaseGroup(context, "builtin_constants", "Built-in Constant Tests")
{
}

ShaderBuiltinConstantTests::~ShaderBuiltinConstantTests (void)
{
}

void ShaderBuiltinConstantTests::init (void)
{
	// Core builtin constants
	{
		static const struct
		{
			const char*												varName;
			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
		} intConstants[] =
		{
			{ "gl_MaxVertexAttribs",					getInteger<GL_MAX_VERTEX_ATTRIBS>						},
			{ "gl_MaxVertexUniformVectors",				getInteger<GL_MAX_VERTEX_UNIFORM_VECTORS>				},
			{ "gl_MaxVertexOutputVectors",				getVectorsFromComps<GL_MAX_VERTEX_OUTPUT_COMPONENTS>	},
			{ "gl_MaxFragmentInputVectors",				getVectorsFromComps<GL_MAX_FRAGMENT_INPUT_COMPONENTS>	},
			{ "gl_MaxFragmentUniformVectors",			getInteger<GL_MAX_FRAGMENT_UNIFORM_VECTORS>				},
			{ "gl_MaxDrawBuffers",						getInteger<GL_MAX_DRAW_BUFFERS>							},

			{ "gl_MaxVertexTextureImageUnits",			getInteger<GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS>			},
			{ "gl_MaxCombinedTextureImageUnits",		getInteger<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS>			},
			{ "gl_MaxTextureImageUnits",				getInteger<GL_MAX_TEXTURE_IMAGE_UNITS>					},

			{ "gl_MinProgramTexelOffset",				getInteger<GL_MIN_PROGRAM_TEXEL_OFFSET>					},
			{ "gl_MaxProgramTexelOffset",				getInteger<GL_MAX_PROGRAM_TEXEL_OFFSET>					},

			{ "gl_MaxImageUnits",						getInteger<GL_MAX_IMAGE_UNITS>							},
			{ "gl_MaxVertexImageUniforms",				getInteger<GL_MAX_VERTEX_IMAGE_UNIFORMS>				},
			{ "gl_MaxFragmentImageUniforms",			getInteger<GL_MAX_FRAGMENT_IMAGE_UNIFORMS>				},
			{ "gl_MaxComputeImageUniforms",				getInteger<GL_MAX_COMPUTE_IMAGE_UNIFORMS>				},
			{ "gl_MaxCombinedImageUniforms",			getInteger<GL_MAX_COMBINED_IMAGE_UNIFORMS>				},

			{ "gl_MaxCombinedShaderOutputResources",	getInteger<GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES>		},

			{ "gl_MaxComputeUniformComponents",			getInteger<GL_MAX_COMPUTE_UNIFORM_COMPONENTS>			},
			{ "gl_MaxComputeTextureImageUnits",			getInteger<GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS>			},

			{ "gl_MaxComputeAtomicCounters",			getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTERS>				},
			{ "gl_MaxComputeAtomicCounterBuffers",		getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS>		},

			{ "gl_MaxVertexAtomicCounters",				getInteger<GL_MAX_VERTEX_ATOMIC_COUNTERS>				},
			{ "gl_MaxFragmentAtomicCounters",			getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTERS>				},
			{ "gl_MaxCombinedAtomicCounters",			getInteger<GL_MAX_COMBINED_ATOMIC_COUNTERS>				},
			{ "gl_MaxAtomicCounterBindings",			getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS>		},

			{ "gl_MaxVertexAtomicCounterBuffers",		getInteger<GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS>		},
			{ "gl_MaxFragmentAtomicCounterBuffers",		getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS>		},
			{ "gl_MaxCombinedAtomicCounterBuffers",		getInteger<GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS>		},
			{ "gl_MaxAtomicCounterBufferSize",			getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE>			},
		};

		static const struct
		{
			const char*													varName;
			ShaderBuiltinConstantCase<tcu::IVec3>::GetConstantValueFunc	getValue;
		} ivec3Constants[] =
		{
			{ "gl_MaxComputeWorkGroupCount",			getIVec3<GL_MAX_COMPUTE_WORK_GROUP_COUNT>				},
			{ "gl_MaxComputeWorkGroupSize",				getIVec3<GL_MAX_COMPUTE_WORK_GROUP_SIZE>				},
		};

		tcu::TestCaseGroup* const coreGroup = new tcu::TestCaseGroup(m_testCtx, "core", "Core Specification");
		addChild(coreGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
			coreGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, DE_NULL));

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ivec3Constants); ndx++)
			coreGroup->addChild(new ShaderBuiltinConstantCase<tcu::IVec3>(m_context, ivec3Constants[ndx].varName, ivec3Constants[ndx].getValue, DE_NULL));
	}

	// OES_sample_variables
	{
		tcu::TestCaseGroup* const sampleVarGroup = new tcu::TestCaseGroup(m_testCtx, "sample_variables", "GL_OES_sample_variables");
		addChild(sampleVarGroup);
		sampleVarGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, "gl_MaxSamples", getInteger<GL_MAX_SAMPLES>, "GL_OES_sample_variables"));
	}

	// EXT_geometry_shader
	{
		static const struct
		{
			const char*												varName;
			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
		} intConstants[] =
		{
			{ "gl_MaxGeometryInputComponents",			getInteger<GL_MAX_GEOMETRY_INPUT_COMPONENTS>			},
			{ "gl_MaxGeometryOutputComponents",			getInteger<GL_MAX_GEOMETRY_OUTPUT_COMPONENTS>			},
			{ "gl_MaxGeometryImageUniforms",			getInteger<GL_MAX_GEOMETRY_IMAGE_UNIFORMS>				},
			{ "gl_MaxGeometryTextureImageUnits",		getInteger<GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS>			},
			{ "gl_MaxGeometryOutputVertices",			getInteger<GL_MAX_GEOMETRY_OUTPUT_VERTICES>				},
			{ "gl_MaxGeometryTotalOutputComponents",	getInteger<GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS>		},
			{ "gl_MaxGeometryUniformComponents",		getInteger<GL_MAX_GEOMETRY_UNIFORM_COMPONENTS>			},
			{ "gl_MaxGeometryAtomicCounters",			getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTERS>				},
			{ "gl_MaxGeometryAtomicCounterBuffers",		getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS>		},
		};

		tcu::TestCaseGroup* const geomGroup = new tcu::TestCaseGroup(m_testCtx, "geometry_shader", "GL_EXT_geometry_shader");
		addChild(geomGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
			geomGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_geometry_shader"));
	}

	// EXT_tessellation_shader
	{
		static const struct
		{
			const char*												varName;
			ShaderBuiltinConstantCase<int>::GetConstantValueFunc	getValue;
		} intConstants[] =
		{
			{ "gl_MaxTessControlInputComponents",			getInteger<GL_MAX_TESS_CONTROL_INPUT_COMPONENTS>		},
			{ "gl_MaxTessControlOutputComponents",			getInteger<GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS>		},
			{ "gl_MaxTessControlTextureImageUnits",			getInteger<GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS>		},
			{ "gl_MaxTessControlUniformComponents",			getInteger<GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS>		},
			{ "gl_MaxTessControlTotalOutputComponents",		getInteger<GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS>	},

			{ "gl_MaxTessEvaluationInputComponents",		getInteger<GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS>		},
			{ "gl_MaxTessEvaluationOutputComponents",		getInteger<GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS>	},
			{ "gl_MaxTessEvaluationTextureImageUnits",		getInteger<GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS>	},
			{ "gl_MaxTessEvaluationUniformComponents",		getInteger<GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS>	},

			{ "gl_MaxTessPatchComponents",					getInteger<GL_MAX_TESS_PATCH_COMPONENTS>				},

			{ "gl_MaxPatchVertices",						getInteger<GL_MAX_PATCH_VERTICES>						},
			{ "gl_MaxTessGenLevel",							getInteger<GL_MAX_TESS_GEN_LEVEL>						},
		};

		tcu::TestCaseGroup* const tessGroup = new tcu::TestCaseGroup(m_testCtx, "tessellation_shader", "GL_EXT_tessellation_shader");
		addChild(tessGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
			tessGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_tessellation_shader"));
	}
}

} // Functional
} // gles31
} // deqp
